package org.dru.clay.rhino.feature.files;

import org.dru.clay.rhino.feature.Feature;
import org.dru.clay.rhino.util.RhinoUtils;
import org.dru.clay.util.file.FileFunctions;
import org.dru.clay.util.file.FileUtils;
import org.dru.clay.util.functional.CollectionUtils;
import org.dru.clay.util.string.StringUtils;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;

/**
 * Files
 * User: joakimd
 * Date: 2013-07-17
 * Time: 13:04
 */
public final class Files extends Feature {
    private static final String[] FUNCTION_NAMES = {
            "Files_path",
            "Files_exists",
            "Files_isDirectory",
            "Files_isFile",
            "Files_parent",
            "Files_list",
            "Files_makeDir",
            "Files_remove",
            "Files_join",
            "Files_copyFile",
            "Files_copyFiles",
            "Files_read",
            "Files_write"
    };

    @Override
    public void init(final Context context, final ScriptableObject scope) throws Exception {
        scope.defineFunctionProperties(FUNCTION_NAMES, Files.class, ScriptableObject.DONTENUM);
        loadFeatureScript(context, scope, Files.class, "File.js");
        loadFeatureScript(context, scope, Files.class, "FileSet.js");
        loadFeatureScript(context, scope, Files.class, "Filter.js");
    }

    public static Object Files_path(final Context context, final Scriptable thisObj,
                                    final Object[] args, final Function funcObj) throws IOException {
        final Collection<String> parts = toStringCollection(args);
        File result = FileUtils.file(parts);
        if (!result.isAbsolute()) {
            result = new File(getString(thisObj, "baseDir"), result.getPath());
        }
        try {
            return result.getCanonicalPath();
        } catch (final IOException exc) {
            throw new IOException(result.getPath(), exc);
        }
    }

    public static Object Files_exists(final Context context, final Scriptable thisObj,
                                      final Object[] args, final Function funcObj) throws IOException {
        return getAbsoluteFile(Context.toString(args[0])).exists();
    }

    public static Object Files_isDirectory(final Context context, final Scriptable thisObj,
                                           final Object[] args, final Function funcObj) throws IOException {
        return getAbsoluteFile(Context.toString(args[0])).isDirectory();
    }

    public static Object Files_isFile(final Context context, final Scriptable thisObj,
                                      final Object[] args, final Function funcObj) throws IOException {
        return getAbsoluteFile(Context.toString(args[0])).isFile();
    }

    public static Object Files_parent(final Context context, final Scriptable thisObj,
                                      final Object[] args, final Function funcObj) throws IOException {
        return getAbsoluteFile(Context.toString(args[0])).getParent();
    }

    public static Object Files_list(final Context context, final Scriptable thisObj,
                                    final Object[] args, final Function funcObj) throws IOException {
        final File dir = getAbsoluteFile(Context.toString(args[0]));
        final Collection<File> result = FileUtils.fileTree(dir);
        return RhinoUtils.fromCollection(context, thisObj, CollectionUtils.transform(result, FileFunctions.path()));
    }

    public static Object Files_makeDir(final Context context, final Scriptable thisObj,
                                       final Object[] args, final Function funcObj) throws IOException {
        final File dir = getAbsoluteFile(Context.toString(args[0]));
        if (dir.isFile()) {
            throw new IOException("can not make directory of a file: " + dir);
        }
        return dir.mkdirs();
    }

    public static Object Files_remove(final Context context, final Scriptable thisObj,
                                      final Object[] args, final Function funcObj) throws IOException {
        return FileUtils.remove(getAbsoluteFile(Context.toString(args[0])));
    }

    public static Object Files_join(final Context context, final Scriptable thisObj,
                                    final Object[] args, final Function funcObj) throws IOException {
        final Collection<String> paths = toStringCollection(args);
        return StringUtils.join(paths, File.pathSeparator);
    }

    public static void Files_copyFile(final Context context, final Scriptable thisObj,
                                      final Object[] args, final Function funcObj) throws IOException {
        final File from = getAbsoluteFile(Context.toString(args[0]));
        final File to = getAbsoluteFile(Context.toString(args[1]));
        FileUtils.copy(from, to);
    }

    public static Object Files_copyFiles(final Context context, final Scriptable thisObj,
                                         final Object[] args, final Function funcObj) throws IOException {
        final File from = getAbsoluteFile(Context.toString(args[0]));
        final File into = getAbsoluteFile(Context.toString(args[1]));
        final Collection<File> files = CollectionUtils.transform(RhinoUtils.toCollection((Scriptable) args[2]), FileFunctions.file());
        final Collection<File> result = FileUtils.copy(from, into, files);
        return RhinoUtils.fromCollection(context, thisObj, CollectionUtils.transform(result, FileFunctions.path()));
    }

    public static Object Files_read(final Context context, final Scriptable thisObj,
                                    final Object[] args, final Function funcObj) throws IOException {
        final File file = getAbsoluteFile(Context.toString(args[0]));
        final FileInputStream in = new FileInputStream(file);
        try {
            final byte[] data = new byte[(int) file.length()];
            int position = 0;
            while (position < data.length) {
                position += in.read(data, position, data.length - position);
            }
            return new String(data, Charset.forName("utf-8"));
        } finally {
            in.close();
        }
    }

    public static void Files_write(final Context context, final Scriptable thisObj,
                                   final Object[] args, final Function funcObj) throws IOException {
        final File file = getAbsoluteFile(Context.toString(args[0]));
        final String text = Context.toString(args[1]);
        final boolean append = Context.toBoolean(args[2]);
        final FileOutputStream out = new FileOutputStream(file, append);
        try {
            out.write(text.getBytes(Charset.forName("utf-8")));
        } finally {
            out.close();
        }
    }

    private static File getAbsoluteFile(final String path) throws IOException {
        final File result = new File(path);
        if (!result.isAbsolute()) {
            throw new Error("relative path");
        }
        return result.getCanonicalFile();
    }
}
